import javax.swing.*;

import jpcap.JpcapCaptor;
import jpcap.NetworkInterface;
import jpcap.packet.*;

import java.io.IOException;
import java.net.*;
import java.util.Enumeration;
import java.util.List;

public abstract class CaptureThread{
	
	private jpcap.NetworkInterface[] my_nic2;
	private jpcap.JpcapCaptor capture;
	private jpcap.JpcapSender send_pack;
	private Packet_Builder read_data;
	private Packet_Builder listentoIP_data;
	private Capture_PacketReceiver jpcap_packets;
	private dataPacket_builder data_pb;
	private Packet packet;
	private boolean listen_next_sequence;
	private ThreadVar threadVar;
	private Object value;
	private utils util = new utils();
	private boolean first_Sent_Packet;
	
	public CaptureThread()
	{		
		my_nic2 = jpcap.JpcapCaptor.getDeviceList();
		try {
			read_data = new Packet_Builder(packet);
			setListentoIP_data(new Packet_Builder(packet));
		} catch (UnknownHostException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		jpcap_packets = new Capture_PacketReceiver();
		data_pb = new dataPacket_builder();
		final Runnable doFinished = new Runnable()
		{
			public void run() { finished(); }
		};
		Runnable doConstruct = new Runnable()
		{
			public void run()
			{
				try
				{
					setValue(construct());
				}finally
				{
					threadVar.clear();
				}
				SwingUtilities.invokeLater(doFinished);
			}
		};
		Thread t = new Thread(doConstruct);
		threadVar = new ThreadVar(t);
	}
	
	/**
	 * Enumeration<NetworkInterface> nic = NetworkInterface.getNetworkInterfaces();		
	 *	my_nic = nic.nextElement().getByIndex(25);
	 *	pr.receivePacket(capture.getPacket());		
	 *	capture.processPacket(1, pr);
	 *
	 * * Class to maintain reference to current worker thread
     * under separate synchronization control.

	 *
	 * @param args
	 * @throws IOException
	 */	
	private static class ThreadVar
	{
		private Thread thread;
		public ThreadVar(Thread t)
		{ 
			thread = t; 
		}
		synchronized Thread get() { return thread; }
		synchronized void clear() { thread = null; }
	}
	
	/** 
     * Set the value produced by worker thread 
     */
	private synchronized void setValue(Object x)
	{
		value = x;
	}
	
	/** 
     * Get the value produced by the worker thread, or null if it 
     * hasn't been constructed yet.
     */
	private synchronized Object getValue()
	{
		return value;
	}
	 /** 
     * Compute the value to be returned by the <code>get</code> method. 
     */

	public abstract Object construct();
	
	/**
     * Called on the event dispatching thread (not on the worker thread)
     * after the <code>construct</code> method has returned.
     */
	public void finished(){ interrupt(); }
	
	 /**
     * Return the value created by the <code>construct</code> method.  
     * Returns null if either the constructing thread or the current
     * thread was interrupted before a value was produced.
     * 
     * @return the value created by the <code>construct</code> method
     */

	public Object get()
	{
		while(true)
		{
			Thread t = threadVar.get();
			if(t == null)
				return getValue();
			
			try
			{
				t.join();
			}catch(InterruptedException e)
			{
				System.out.println(e);
			}
		}
	}
	
	/**
     * A new method that interrupts the worker thread.  Call this method
     * to force the worker to stop what it's doing.
     */
	public void interrupt()
	{
		Thread t = threadVar.get();
		if(t != null)
		{
			t.interrupt();			
		}
		threadVar.clear();
	}
	
	/**
     * Start a thread that will call the <code>construct</code> method
     * and then exit.
     */
	public void start()
	{
		Thread t = threadVar.get();
		if(t !=null)
			t.start();
	}
	/**
	 * This starts the capture
	 * listen_next_sequence is used to watch for the next sequence or packet to send back as a reply.
	 * @param i
	 * @param listentoIP
	 * @param protocol
	 * @return
	 */
	public int startcapture(NetworkInterface i,String listentoIP,String protocol)
	{
		jpcap.NetworkInterface nic = i;
		try{
				if(capture==null)
					capture = jpcap.JpcapCaptor.openDevice(nic, -1, false, 400);
				
				capture.processPacket(1, jpcap_packets);
				packet = jpcap_packets.getPacket();				
				if(packet != null)
				{
					if(packet.header.length > 0)
					{		
						read_data.setPkt(packet);
						/**
						 * Save a Packet from the target to send back watch data will only watch for source IP of target..
						 * Target Ack_Num = Sequence
						 * Target Sequence = Ack_Num 						
						if(listen_next_sequence) 
							{
							if(protocol.compareTo("TCP/IPV4")==0 && read_data.protocol==6 && (read_data.dst_ip.getHostAddress().compareTo(listentoIP)==0
									|| read_data.src_ip.getHostAddress().compareTo(listentoIP)==0))	getListentoIP_data().setPkt(packet);
							if(protocol.compareTo("UDP")==0 && read_data.protocol==17 && (read_data.dst_ip.getHostAddress().compareTo(listentoIP)==0
									|| read_data.src_ip.getHostAddress().compareTo(listentoIP)==0))	getListentoIP_data().setPkt(packet);
							if(protocol.compareTo("ARP")==0 && read_data.frametype==2054 && 
									(util.ConvertIPAddress(read_data.sender_protoaddr).compareTo(listentoIP)==0 ||util.ConvertIPAddress(read_data.target_protoaddr).compareTo(listentoIP)==0 ))	getListentoIP_data().setPkt(packet);
							}
						*/
						return 1;
					}
				}	
			}catch(Exception e){ System.out.println(e); }
		return 0;		
	}	
	/**
	 * sends TCP Packet
	 * @param i
	 * @param src_IP
	 * @param dst_IP
	 * @param dst_MAC
	 * @param protocol_Type
	 * @param src_port
	 * @param dst_port
	 * @param urg
	 * @param ack
	 * @param psh
	 * @param rst
	 * @param syn
	 * @param fin
	 */
	public void sendPacket(NetworkInterface i,String src_IP, String dst_IP, String dst_MAC, String protocol_Type,int src_port,int dst_port
			,boolean urg,boolean ack, boolean psh, boolean rst, boolean syn, boolean fin)
	{
		Packet_Builder pb = getListentoIP_data();
		try {
			send_pack = jpcap.JpcapSender.openDevice(i);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		EthernetPacket ep = new EthernetPacket();
		ep.src_mac = i.mac_address;
		if(protocol_Type.compareTo("TCP/IPV4")==0)
		{
			TCPPacket tp;
			if(isFirst_Sent_Packet()){			   
				long seq = (int)(Math.random() * (2147483647 - 0) + 0);
				//seq = seq + (int)(Math.random() * (2147483647 - 0) + 0);
				tp = pb.createTCPPacket(src_port, dst_port,src_IP, dst_IP,urg,ack,psh,rst,syn,fin,seq,0);
			}
			else
			{
				long ack_num = pb.sequence;
				long seq_num = pb.ack_num;
				if(pb.pkt.data!=null)	seq_num = (pb.pkt.data!=null)? (ack_num + pb.pkt.data.length) : ack_num++;
				tp = pb.createTCPPacket(src_port, dst_port,src_IP, dst_IP,urg,ack,psh,rst,syn,fin,seq_num,ack_num);
			}
						
			ep.dst_mac = util.StringtoMacAddress(dst_MAC);	
			ep.frametype = read_data.ETHERTYPE_IP;	
			tp.datalink = ep;			
			send_pack.sendPacket(tp);	
		}		
		send_pack.close();		
	}
	/**
	 * send UDP Packet
	 * @param i
	 * @param src_IP
	 * @param dst_IP
	 * @param dst_MAC
	 * @param protocol_Type
	 * @param src_port
	 * @param dst_port
	 * @param dns_request
	 */
	public void sendPacket(NetworkInterface i,String src_IP, String dst_IP, String dst_MAC, String protocol_Type,int src_port,int dst_port
			,String dns_request)
	{
		Packet_Builder pb = getListentoIP_data();
		try {
			send_pack = jpcap.JpcapSender.openDevice(i);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		EthernetPacket ep = new EthernetPacket();
		ep.src_mac = i.mac_address;
		
		if(protocol_Type.compareTo("UDP")==0)
		{
			UDPPacket udp = pb.createUDPPacket(src_port, dst_port,src_IP, dst_IP);			
			ep.dst_mac = util.StringtoMacAddress(dst_MAC);	
			ep.frametype = read_data.ETHERTYPE_IP;	
			udp.datalink = ep;		
			send_pack.sendPacket(udp);	
		}
		if(protocol_Type.compareTo("DNS_REQUEST")==0)
		{
			UDPPacket udp = pb.createUDPPacket(src_port, dst_port,src_IP, dst_IP);			
			ep.dst_mac = util.StringtoMacAddress(dst_MAC);	
			ep.frametype = read_data.ETHERTYPE_IP;	
			udp.datalink = ep;				
			udp.data = data_pb.dnsRequest(dns_request);
			send_pack.sendPacket(udp);	
		}
		send_pack.close();	
	}
	/**
	 * Send ARPPacket
	 * @param i
	 * @param src_IP
	 * @param dst_IP
	 * @param dst_MAC
	 */
	public void sendPacket(NetworkInterface i,String src_IP, String dst_IP,String src_MAC, String dst_MAC,short hardware,short opCode)
	{
		Packet_Builder pb = getListentoIP_data();		
		try {
			send_pack = jpcap.JpcapSender.openDevice(i);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		EthernetPacket ep = new EthernetPacket();
		ep.src_mac = util.StringtoMacAddress(src_MAC);		
		ep.dst_mac = util.StringtoMacAddress("ff:ff:ff:ff:ff:ff");
		
		ARPPacket arp = pb.createARPPacket(ep.src_mac,util.StringtoIPAddress(src_IP),
				util.StringtoMacAddress(dst_MAC),util.StringtoIPAddress(dst_IP),hardware,opCode);		
		
		ep.frametype = read_data.ETHERTYPE_ARP;			
		arp.datalink = ep;
		send_pack.sendPacket(arp);
		send_pack.close();
	}	
	/**
	 * Send ICMP Packet
	 * @param i
	 * @param src_IP
	 * @param dst_IP
	 * @param src_MAC
	 * @param dst_MAC
	 * @param hardware
	 * @param opCode
	 */
	public void sendPacket(NetworkInterface i,String src_IP, String dst_IP,String src_MAC, String dst_MAC,byte type, byte code)
	{
		Packet_Builder pb = getListentoIP_data();		
		try {
			send_pack = jpcap.JpcapSender.openDevice(i);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		EthernetPacket ep = new EthernetPacket();
		ep.src_mac = util.StringtoMacAddress(src_MAC);
		ep.dst_mac = util.StringtoMacAddress(dst_MAC);
		ep.frametype = read_data.ETHERTYPE_IP;		
		ICMPPacket icmp = pb.createICMPPacket(src_IP, dst_IP, type, code);
		icmp.datalink = ep;
		send_pack.sendPacket(icmp);
		send_pack.close();		
	}
	/** Current NIC's */
	public jpcap.NetworkInterface[] getMy_nic2() {	return my_nic2;	}
	public void setMy_nic2(jpcap.NetworkInterface[] my_nic2) {	this.my_nic2 = my_nic2;	}
	/**Packet captured */
	public Packet_Builder getRead_data() {	return read_data;	}
	public void setRead_data(Packet_Builder read_data) {	this.read_data = read_data;	}
	/**Watch for the next seq and ack number  */
	public boolean isListen_next_sequence() {	return listen_next_sequence;	}
	public void setListen_next_sequence(boolean listen_next_sequence) {		this.listen_next_sequence = listen_next_sequence;	}
	/**Save our next packet which contains the seq and ack number */
	public Packet_Builder getListentoIP_data() {	return listentoIP_data;	}
	public void setListentoIP_data(Packet_Builder listentoIP_data) {	this.listentoIP_data = listentoIP_data;	}
	/**Check if this packets is sent for the first time so we know to retrieve or create a sequence number */
	public void setFirst_Sent_Packet(boolean first_Sent_Packet) {	this.first_Sent_Packet = first_Sent_Packet;	}
	public boolean isFirst_Sent_Packet() {	return first_Sent_Packet;	}
	
	
}
